This guide walks you through integrating the Maven plugin for Native Image into your project. It starts from enabling the plugin, building the first native image, and running it.
Then it takes you to more advanced use-cases such as plugin’s configuration, applying optimizations, running native tests, and troubleshooting. If you are an advanced user, you can skip the getting started part and go directly to the advanced section.
Getting Started
To compile your application ahead of time with GraalVM Native Image and Maven, enable the Maven plugin for Native Image building. The plugin requires that you install GraalVM.
Add the Plugin
Add the plugin declaration to your pom.xml:
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>${native.maven.plugin.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
<configuration>
<mainClass>org.example.Main</mainClass>
</configuration>
</plugin>
For convenience, you can create a Maven profile and add the plugin into it:
<profiles>
<profile>
<id>native</id>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>${native.maven.plugin.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
<configuration>
<mainClass>org.example.Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
Replace maven-plugin-version
with the latest released version.
All plugin versions are listed here.
The <mainClass>
tag provides the path to the application main class (the main entry point).
Adjust the path according to your application sources.
Build and Run Your Application
Once you registered the plugin, you can use the standard Maven phases.
If you added the plugin inside the native
profile, run your commands with the -Pnative
option.
-
Build a native executable of your application. This command will compile your application and create a native executable in the target/ directory:
./mvnw -Pnative package
-
Run the application from the native executable:
./target/myApp
You can have multiple profiles, which is very convenient if you want to produce different versions of your native images for your application (optimized, static, and others). Continue to advanced use cases to learn more.
Advanced Use Cases: How to
For advanced use cases, this guide provides instructions for configuring the build process, running tests on native code, gathering execution profiles, troubleshooting missing configuration, and enabling diagnostic tools to analyze native images.
Configure Native Image Build
The plugin supports passing options directly to Native Image inside the <configuration>
block.
Using <buildArg>
, you can pass any Native Image build option listed on this page.
The plugin also provides special properties to configure the build:
-
<environment>
- Sets the environment options -
<imageName>
- Specifies of the name for the native executable file. If a custom name is not supplied, the artifact ID of the project will be used by default (defaults to the project name). -
<jvmArgs>
- Passes the given argument directly to the JVM running thenative-image
tool -
<quickBuild>
- Enables quick build mode -
<verbose>
- Enables the verbose output -
and many more listed here.
Here is an example of additional options usage:
<configuration>
<mainClass>org.example.Main</mainClass>
<imageName>myApp</imageName>
<verbose>true</verbose>
<buildArgs>
<buildArg>-O3</buildArg> <!-- enables additional compiler optimizations -->
</buildArgs>
<environment>
<variable1>value1</variable1>
<variable2>value2</variable2>
</environment>
<jvmArgs>
<arg>your-argument</arg>
</jvmArgs>
</configuration>
As an alternative, you can pass additional build options via the |
Learn more about Native Image build configuration on the website.
Run Junit Tests
This plugin supports running tests on the JUnit Platform. The tests are compiled ahead of time and executed as native code.
-
To execute tests, add one more
<execution>
to the plugin declaration:
<execution>
<id>test-native</id>
<goals>
<goal>test</goal>
</goals>
<phase>test</phase>
</execution>
-
Add the JUnit 5 dependency to pom.xml to include the testing framework. It will only be used during the
test
phase and not included in the final build artifact:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
-
Add the Maven Surefire Plugin into the
plugins
section of thenative
profile:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0</version>
</plugin>
-
Run the tests:
./mvnw -Pnative test
The new execution you have just added, test-native
, integrates with the Maven test
phase.
First, Maven runs the tests on the JVM, then compiles them ahead of time and executes them as native code.
Disable tests
If you wish to disable tests on the JVM as well as running native code tests, invoke Maven with the -DskipTests
flag.
This flag is supported by the Maven Surefire plugin and Native Build Tools.
./mvnw -Pnative -DskipTests package
If you wish to run tests on the JVM with the Maven Surefire plugin, but skip running tests as native code, invoke Maven with the -DskipNativeTests
flag.
This flag is specific to Native Build Tools.
./mvnw -Pnative -DskipNativeTests package
Alternatively, set <skipNativeTests>
to true
in the plugin configuration:
<configuration>
<skipNativeTests>true</skipNativeTests>
</configuration>
This way you configure your Maven profile to skip generation and execution of tests as native code.
Gather Execution Profiles and Build Optimized Images
You may want to gather profiling information from your application’s execution to pinpoint areas of inefficiency. With this profiling data, you can also build an optimized native image.
The technique for building native images optimized on profiles is called Profile-Guided Optimization (PGO). With PGO you can “train” your native application for specific workloads to improve performance and throughput. The PGO workflow includes three steps.
PGO is available in Oracle GraalVM. |
Step 1: Build an instrumented native image by passing the --pgo-instrument
option to native-image
using <buildArg>
.
To prevent overwriting a previously built native executable, we recommend either creating a separate Maven profile for each build or specifying a unique file name using the <imageName>
tag.
For example:
<configuration>
<imageName>instrumentedApp</imageName>
<buildArgs>
<buildArg>--pgo-instrument</buildArg>
</buildArgs>
</configuration>
Run the build command:
./mvnw -Pnative package
Step 2: Gather profiles by running the instrumented executable. By default, the default.iprof file, if not specified otherwise, is generated alongside the native executable.
./target/instrumentedApp
Step 3. Build an optimized native image with profiles by passing the --pgo
option.
You may want to provide a different name for the native image or create another Maven profile to handle this configuration:
<configuration>
<imageName>optimizedApp</imageName>
<buildArgs>
<buildArg>--pgo</buildArg>
</buildArgs>
</configuration>
Run the build command:
./mvnw -Pnative package
If the profile file has the default name and location, it will be automatically picked up.
Alternatively, you can specify the file path as following: --pgo=myprofile.iprof
.
If everything was done properly, you will see "PGO: user-provided" in the native image build output.
Once the optimized image is built, run it: ./target/optimizedApp
.
The application’s performance when running from this native executable should be comparable to, or even faster than, running on the JVM.
Learn more about PGO on the website.
Troubleshoot Missing Configuration
Detect Missing Metadata
Quite possibly, your application relies on external libraries. If your application uses a well-supported framework such as Spring or Micronaut, its dependencies should be compatible with Native Image. Frameworks and libraries that support Native Image by default provide configurations in the GraalVM Reachability Metadata Repository. When you build a native image, Native Build Tools reference this repository to apply the required configuration automatically.
You can find an extensive list of libraries and frameworks from the Java ecosystem tested with Native Image on this page. |
However, it may happen, that your native image crashes at run time with a missing class or resource. To address this, start by checking if any required configuration is missing.
The best way to detect missing metadata is by running your native tests. Alternatively, you can identify missing configuration manually using the following method.
-
Pass the
--exact-reachability-metadata
option to thenative-image
tool in pom.xml, as shown below:
<configuration>
<buildArgs>
<buildArg>--exact-reachability-metadata</buildArg>
</buildArgs>
</configuration>
The |
-
Rebuild the application:
./mvnw -Pnative package
-
Run the application from the native executable with the
-XX:MissingRegistrationReportingMode=Warn
option:
./target/myApp -XX:MissingRegistrationReportingMode=Warn
With GraalVM versions older than JDK 23, pass |
-
If there is any missing metadata printed to the console, add it to the configuration file manually, as described here, or collect it automatically using the Tracing agent. (See next.)
-
Rebuild your native image and test again.
Collect Metadata Automatically with Tracing Agent
Your application may use dynamic Java features such as reflection, serialization, or resource loading. It is also possible that a framework your application relies on uses a library dependency incompatible with Native Image. In such cases, additional metadata is required.
The easiest way to collect the missing metadata is by using the Tracing Agent. This agent tracks all usages of dynamic features during application execution on the JVM and generates the necessary configuration.
This guide demonstrates how to generate metadata from your tests.
Generating metadata from your main application requires more configuration. The process is otherwise identical, except that you use the |
The agent is disabled by default. You can enable it on the command line or in pom.xml.
To enable the agent via the command line, pass the -Dagent=true
option when running Maven:
./mvnw -Pnative -Dagent=true test
Enabling the agent via the command line only attaches it for a specific run; it does not automatically run every time you build the application. |
To enable the agent in pom.xml and collect missing metadata, do the following.
Step 1: Enable the agent by setting <agent>
to true
in the native
profile:
<configuration>
<agent>
<enabled>true</enabled>
</agent>
</configuration>
From that point on, commands you execute will run with the agent attached.
Step 2: Copy the generated metadata from the default location, target/native/agent-output, to the resources directory, for example, resources/META-INF/native-image. Native Image automatically uses the metadata from this location.
To do that with Maven, configure and run the metadataCopy
task.
Add a new task named metadataCopy
inside the agent
block that you added in step 1.
Your agent
configuration should look like this:
<agent>
<enabled>true</enabled>
<metadataCopy>
<disabledStages>
<stage>main</stage>
</disabledStages>
<merge>true</merge>
<outputDirectory>src/test/resources/META-INF/native-image</outputDirectory>
</metadataCopy>
</agent>
In this block:
-
<outputDirectory>
specifies location where you want to copy the generated metadata. -
<disableStages>
- you can disable metadata copy for a concrete Maven phase. In this you do not want the agent output from themain
phase. -
<merge>
- specifies whether the metadata you want to copy, should be merged with the metadata that already exists in the given location, or not. This only makes sense when there is already some existing metadata, created before.
Step 3: Now that the metadataCopy
task is configured, run the agent to collect the metadata and copy it to the other location:
./mvnw -Pnative test native:metadata-copy
Step 4: Finally, proceed without the agent and build the native image with the metadata. From that point on, you can run your tests with:
./mvnw -Pnative test
If your native image is successfully build, but still fails at run time, check the troubleshooting guide Troubleshoot Native Image Run-Time Errors.
Learn more about how to fine-tune the agent further here.
Use Diagnostics Tools
If you need to diagnose the native applications you build, or monitor your Java application when launched from a native executable, Native Image offers tools for debugging and analyzing the produced binary. For example:
<configuration>
<debug>true</debug>
<buildArgs>
<buildArg>--emit build-report</buildArg>
<buildArg>--enable-monitoring=jfr</buildArg>
</buildArgs>
</configuration>
-
The
--emit build-report
option generates an HTML page report alongside the native executable that you can open in a browser. It provides broad information about each build stage as well as the generated binary’s contents. You can read more about Build Report features here.
Build Report is available in Oracle GraalVM.
When running on GraalVM for JDK 21, pass the |
-
The
--enable-monitoring=jfr
instructs the plugin to build a native executable with the JDK Flight Recorder (JFR) support. -
The
<debug>
option generates a native executable with debug information for source-level debugging with the GNU Debugger (GDB).
All the monitoring and debugging tools listed on the website, can be enabled in the plugin configuration using <buildArgs>
.
You will find the output of these tools among the generated artifacts after running:
./mvnw -Pnative package
Learn more
To continue learning, refer to the extensive reference documentation for the GraalVM Native Image Maven plugin.